﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Windows.Controls.Primitives;

namespace GoodStuff.Silverlight
{
    /// <summary>
    /// Automatically binds properties and methods of the ViewModel to the view and initializes the datacontext.
    /// </summary>
    /// <remarks>
    /// Courtesy of Rob Eisenberg
    /// http://live.visitmix.com/MIX10/Sessions/EX15
    /// </remarks>
    public class ViewModelBinder
    {
        private static Dictionary<Type, DependencyProperty> __defaultBindings = new Dictionary<Type, DependencyProperty>() 
        {
            { typeof(TextBlock), TextBlock.TextProperty },
            { typeof(TextBox), TextBox.TextProperty },
            { typeof(ListBox), ListBox.ItemsSourceProperty },
            { typeof(Panel), Panel.VisibilityProperty },
            { typeof(Border), Border.VisibilityProperty },
            { typeof(TabControl), TabControl.ItemsSourceProperty },
            { typeof(ItemsControl), ItemsControl.ItemsSourceProperty },
            { typeof(ContentControl), DynamicTemplate.ModelProperty },
            { typeof(TransitionControl), DynamicTemplate.ModelProperty },
            { typeof(CheckBox), CheckBox.IsCheckedProperty },
            { typeof(ComboBox), ComboBox.SelectedItemProperty },
            { typeof(DataGrid), DataGrid.ItemsSourceProperty },
            { typeof(AutoDataGrid), DataGrid.ItemsSourceProperty },
            { typeof(Image), Image.SourceProperty }
        };

        public static void RegisterBinding(Type t, DependencyProperty property)
        {
            __defaultBindings.Add(t, property);
        }
    
        public static void Bind(ViewModelBase viewModel, DependencyObject viewObject)
        {
            FrameworkElement view = viewObject as FrameworkElement;
            if (view == null)
            {
                return;
            }

            //enumerate all public properties of the viewModel and bind them to the view.
            var properties = viewModel.GetType().GetProperties();
            var methods = viewModel.GetType().GetMethods();
            BindProperties(properties, methods, viewModel, view);
            BindMethods(methods, properties, viewModel, view);

            //set the datacontext of the view to the model.
            view.DataContext = viewModel;            
        }

        private static void BindMethods(MethodInfo[] methods, PropertyInfo[] properties, object model, FrameworkElement view)
        {
            //Convention is that every public method of the ViewModel corresponds to an element that can
            //execute an event; for now: only Buttons are supported.

            foreach (var method in methods)
            {
                DependencyObject element = view.FindName(method.Name) as DependencyObject;
                if (element == null)
                {
                    continue;
                }

                //find the Can... property.
                PropertyInfo canExecute = properties.FirstOrDefault(p => p.Name == "Can" + method.Name);

                ReflectiveCommand cmd = new ReflectiveCommand(model, method, canExecute);

                //Set the command. Unfortunately, Command Binding is not available in SL3 so we have to do this manually
                TrySetBindingButtonBase(element, cmd);
            }
        }

        private static void TrySetBindingButtonBase(DependencyObject element, ReflectiveCommand cmd)
        {
            ButtonBase button = element as ButtonBase;
            if (button != null)
            {
                button.Click += delegate { cmd.Execute(null); };
                //wire up canExecute.
                cmd.CanExecuteChanged += (s, e) => { button.IsEnabled = cmd.CanExecute(null); };

                //initialize the availability
                button.IsEnabled = cmd.CanExecute(null);
            }
        }     

        private static void BindProperties(PropertyInfo[] properties, MethodInfo[] methods, object model, FrameworkElement view)
        {
            foreach (var property in properties)
            {
                DependencyObject element = view.FindName(property.Name) as DependencyObject;
                if (element == null)
                {
                    continue;
                }

                DependencyProperty boundProperty;

                // If we have no mapping, skip.
                if(__defaultBindings.TryGetValue(element.GetType(), out boundProperty) == false)
                    continue;

                // If a dependencyObject has a custom binding expression, skip.
                if (((FrameworkElement)element).GetBindingExpression(boundProperty) != null)
                    continue;

                Binding newBinding = new Binding(property.Name)
                {
                    Mode = (property.CanWrite) ? BindingMode.TwoWay : BindingMode.OneWay
                    //newBinding.NotifyOnValidationError = Attribute.GetCustomAttributes(property, typeof(ValidationAttribute), true).Any()
                };

                var tabControl = element as TabControl;
                if (tabControl != null)
                {
                    newBinding.Converter = new TabItemConverter();
                }
                if (boundProperty == Border.VisibilityProperty || boundProperty == Panel.VisibilityProperty)
                {
                    newBinding.Converter = new VisibilityConverter();
                }
                var imageControl = element as Image;
                if (imageControl != null)
                {
                    newBinding.Converter = new ImageSourceConverter();
                }

                BindingOperations.SetBinding(element, boundProperty, newBinding);

                //Trigger a refresh on every key instead of LostFocus
                TextBox textBoxElement = element as TextBox;
                if (textBoxElement != null)
                {
                    textBoxElement.TextChanged += delegate { textBoxElement.GetBindingExpression(TextBox.TextProperty).UpdateSource(); };
                    continue;
                }

                var itemsComboBox = element as ComboBox;
                if (itemsComboBox != null && itemsComboBox.ItemsSource == null)
                {
                    if (properties.SingleOrDefault(p => p.Name == property.Name + "Choices") != null)
                    {
                        Binding choicesBinding = new Binding(property.Name + "Choices");
                        BindingOperations.SetBinding(element, ComboBox.ItemsSourceProperty, choicesBinding);
                    }
                }

                var itemsControl = element as ItemsControl;
                if (itemsControl != null && string.IsNullOrEmpty(itemsControl.DisplayMemberPath) && itemsControl.ItemTemplate == null && newBinding.Converter == null)
                {
                    itemsControl.ItemTemplate = DynamicTemplate.GetDynamicTemplate();
                }            

                var gridControl = element as DataGrid;
                if (gridControl != null)
                {
                    // when this is a grid-control and a matching method exists, dynamically add a command to handle row
                    // select.
                    if(methods.Any(p => p.Name == property.Name + "Selected"))
                    {
                        var cmd = new ReflectiveCommand(model, property.Name + "Selected");
                        gridControl.MouseLeftButtonUp += (s, e) => { cmd.Execute(gridControl.SelectedItem); };
                    }
                }
            }
        }
    }
}
